var rewire = require("rewire")
const R = require('ramda')
const {sleep} = rewire('./utils.cjs')
const {getLogger} = rewire('../common/logger.cjs')
const logger = getLogger('combinator')

function composeWithMapAsync(f, ...fns) {
  if (fns.length === 0) {
    return f
  }
  return composeWithMapAsync(
    ...[async function(...args) {
      const rs = await f(...args)
      const f1 = fns[0]
      const rfield = fns[1]
      const sfield = fns[2]
      rs[sfield] = await f1(rs[rfield])
      return rs
    }, ...fns.slice(3)]
  )
}

function composeAsync(...fns) {
  if (fns.length === 1) {
    return fns[0]
  }
  return composeAsync(
    ...[async function(...args) {
      const rs = await fns[0](...args)
      const f1 = fns[1]
      return await f1(rs)
    }, ...fns.slice(2)]
  )
}

function wrapFnAsync(f, wrap) {
  return async function(...args) {
    return await wrap(f, ...args)
  }
}

function whenNoError(fn, onNoError, onError = null) {
  return async function(...args) {
    try {
      const rs = await fn(...args)
      return onNoError(rs, args)
    } catch(e) {
      if (onError != null) {
        return onError(e, args)
      }
      throw e
    }
  }
}


function makeUntilCaller(f, until) {
  async function theFunc() {
    const rs = await f()
    if (until(rs)) {
      return rs
    }
    return await theFunc()
  }
  return theFunc
}

function makeIncreaseCaller(f, startNum) {
  let num = startNum
  return async function() {
    const rs = await f(num)
    num = num + 1
    return rs
  }
}

function makeStateIncreaseCaller(f, findI, saveI) {
  let num = -1
  return async function() {
    if (num < 0) {
      num = await findI()
    }
    const rs = await f(num)
    await saveI(num)
    num = num + 1
    return rs
  }
}

function composeEachAsync(f, itemFn) {
  return async function(...args) {
    const list = await f(...args)
    let result = []
    for (let i in list) {
      const item = list[i]
      result[i] = await itemFn(item)
    }
    return result
  }
}


function errorRetry(f, times, errorCb = null, interval = 0) {
  return async function theFn(...args) {
    async function loop(times) {
      if (times <= 0) {
        throw new Error('重试次数为0，不再重试了')
      }
      try {
        return await f(...args)
      } catch (error) {
        if (errorCb) {
          await errorCb(error)
        }
        if (interval > 0) {
          await sleep(interval)
        }
        return await loop(times - 1)
      }
    }
    return await loop(times)
  }
}

// 生成一个方法，调用该方法如果抛出异常，忽略并记录次数，如超出错误次数则继续往外跑异常
function totalError(f, onError, times) {
  let error = 0
  return async function(...args) {
    if (error >= times) {
      throw new Error("该方法已报错超过 " + times + " 次")
    }
    try {
      return await f(...args)
    } catch (e) {
      logger.error(e)
      error++
      return await onError(e)
    }
  }
}

function parallelComposeAsync(comb, ...fns) {
  return async function(...args) {
    let rsList = []
    for (let i in fns) {
      const fn = fns[i]
      rsList[i] = await fn(...args)
    }
    return await comb(...rsList)
  }
}

function parallelCompose(comb, ...fns) {
  return function(...args) {
    let rsList = []
    for (let i in fns) {
      const fn = fns[i]
      rsList[i] = fn(...args)
    }
    return comb(...rsList)
  }
}


function identity(obj) {
  return obj
}

async function filterAsync(ls, asyncFn) {
  let result = []
  const len = ls.length
  for (let i = 0; i < len; i++) {
    const item = ls[i]
    const b = await asyncFn(item)
    if (b) {
      result.push(item)
    }
  }
  return result
}


function makeNotFn(f) {
  return async function(...args) {
    return !(await f(...args))
  }
}


function createParallelRunner(cutFn, data, doItem) {
  return async function() {
    const arrList = cutFn(data)
    await Promise.all(
      arrList.map(async (arr) => {
        for (let info of arr) {
          await doItem(info)
        }
      })
    )
  }
}

const splitArray = (arr, size)=>{  // size=5，要分割的长度
  const arrNum = Math.ceil(arr.length/size, 10) // Math.ceil()向上取整的方法，用来计算拆分后数组的长度
  let index = 0; // 定义初始索引
  let resIndex = 0; // 用来保存每次拆分的长度
  const result = [];
  while(index < arrNum){
    result[index]= arr.slice(resIndex,size+resIndex)
    resIndex += size
    index++
  }
  return result;
}

function logParams(fn) {
  return async function(...args) {
    logger.info("The args: " + args)
    return await fn(...args)
  }
}

async function parallelMap(n, f, data) {
  const splitData = splitArray(data, n)
  let rs = []
  for (let itemList of splitData) {
    const itemRs = await Promise.all(itemList.map(f))
    rs = rs.concat(itemRs)
  }
  return rs
}

async function parallelIter(n, iter, data) {
  const splitData = splitArray(data, n)
  for (let itemList of splitData) {
    await Promise.all(itemList.map(iter))
  }
}


function partial(f, ...args) {
  return async (...params) => {
    return await f(...args, ...params)
  }
}

async function asyncMap(f, ...data) {
  const promiseList = R.map(f, ...data)
  return await Promise.all(promiseList)
}


async function increaseCallerCatch(start, end, f) {
  return await parallelMapCatch(1, f, R.range(start, end))
}


async function parallelMapCatch(n, f, data) {
  let rs = []
  const splitData = splitArray(data, n)
  for (let itemList of splitData) {
    try {
      const itemRs = await Promise.all(itemList.map(f))
      rs = rs.concat(itemRs)
    }
    catch(err) {
      if (err.type == 'NextError') {
        const name = err.name
        if (name === 'continue') {
          continue
        }
        else if (name === 'break') {
          break
        }
      }
      else {
        logger.error(err)
        throw err
      }
    }
  }
  return rs
}

async function mapUntil(f, start, untilFn) {
  return await mapIncreaseUntil(f, start, (num) => {return num + 1}, untilFn)
}

async function mapIncreaseUntil(f, start, incFn, untilFn) {
  let result = []
  let current = start
  while(true) {
    const data = await f(current)
    result.push(data)
    if (untilFn(data)) {
      return result
    }
    current = incFn(current)
  }
}

/**
 * 收集参数和结果，封装到结果Map中
 */
function collectParamsAndResult(f, names) {
  return async function(...args) {
    const rs = await f(...args)
    let data = {}
    for (i = 0; i < args.length; i++) {
      data[names[i]] = args[i]
    }
    data[names[names.length - 1]] = rs
    return data
  }
}


module.exports = {
  composeWithMapAsync, 
  composeAsync, 
  wrapFnAsync, 
  makeUntilCaller, 
  makeIncreaseCaller,
  makeStateIncreaseCaller, 
  composeEachAsync, 
  errorRetry, 
  parallelComposeAsync, 
  parallelCompose, 
  identity, 
  filterAsync, 
  makeNotFn, 
  createParallelRunner, 
  parallelMap, 
  partial, 
  asyncMap, 
  increaseCallerCatch, 
  parallelMapCatch,
  mapUntil, 
  mapIncreaseUntil, 
  collectParamsAndResult, 
  logParams,
  whenNoError,
  parallelIter
}
